package bbcom.traversing;

import bbcom.*;
import bbcom.control.BBCoMSettings;
import bbcom.interfaces.IContainer;
import bbcom.interfaces.IImplementationParser;
import bbcom.interfaces.IPropertiesParser;
import bbcom.messages.BBCoMMessageResource;
import bbcom.messages.BBCoMMessageUtility;
import bbcom.messages.BBCoMTraversingException;
import bbcom.modeling.BBCoMModeling;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by Thanh-Trung Pham.
 */
public class BBCoMLocalTraversing {
    private String m_strCurrentDeviceContainerName;

    private BBCoMModeling m_oBBCoMModeling;

    private IContainer m_oIContainer;


    /**
     * The constructor.
     *
     * @param bbComModeling              the BBCoMModeling to traverse on.
     * @param currentDeviceContainerName the current local device container's name.
     * @throws BBCoMTraversingException if there is an error.
     */
    public BBCoMLocalTraversing(BBCoMModeling bbComModeling, String currentDeviceContainerName) throws BBCoMTraversingException {
        if (!bbComModeling.isValid()) {
            throw new BBCoMTraversingException(BBCoMMessageUtility.getResourceMessage(BBCoMMessageResource.BBCOMTRAVERSING_INVALID_BBCOMINSTANCE_TO_TRAVERSE));
        }

        m_oBBCoMModeling = bbComModeling;
        m_strCurrentDeviceContainerName = currentDeviceContainerName;
    }

    /**
     * Traverse the given BBCoM instance within the BBCoM modeling.
     *
     * @throws BBCoMTraversingException if there is an error.
     */
    public void traverse() throws BBCoMTraversingException {
        DeploymentType oDeployment = m_oBBCoMModeling.getDeployment();


        DeviceEnvironmentType oDeviceEnvironment = m_oBBCoMModeling.getDeviceEnvironment(oDeployment);
        DeviceContainerType oDeviceContainer = m_oBBCoMModeling.getDeviceContainer(oDeviceEnvironment, m_strCurrentDeviceContainerName);
        SystemType oSystem = m_oBBCoMModeling.getSystem(oDeployment);

        try {
            BBCoMSettings oBBCoMSettings = BBCoMSettings.getInstance();
            IPropertiesParser oIProperty = oBBCoMSettings.getPropertiesParserPlugin();

            Map<String, String> oAllDeviceContainersIPAddresses = new HashMap<>();
            DeviceContainerType[] oDeviceContainers = m_oBBCoMModeling.getAllDeviceContainers(oDeviceEnvironment);
            for (int i = 0; i < oDeviceContainers.length; i++) {

                String strContainerName = oDeviceContainers[i].getName();
                PropertiesType oContainerProperties = oDeviceContainers[i].getProperties();
                String strContainerIPAddress = oIProperty.getHost(oContainerProperties);

                oAllDeviceContainersIPAddresses.put(strContainerName, strContainerIPAddress);
            }

            m_oIContainer = oBBCoMSettings.getNewInstanceOfContainerPlugin();
            m_oIContainer.setConfigurationSettings(oSystem.getName(), oDeviceContainer, oAllDeviceContainersIPAddresses, m_oBBCoMModeling.getBaseFolderName());
            m_oIContainer.setPlugins(oBBCoMSettings.getImplementationParserPlugin(), oBBCoMSettings.getUserInterfaceParserPlugin(), oBBCoMSettings.getPropertiesParserPlugin());

            List<String> oSortedAllocationNames = BBCoMSharedTraversing.getSortedAllocationNames(m_oBBCoMModeling.getScenario(oDeployment), m_oBBCoMModeling.getAllocationNames(oDeployment));
            List<DeployableEntityType> oDeployableEntities = new ArrayList<>();

            for (int i = 0; i < oSortedAllocationNames.size(); i++) {
                DeployableEntityType oDeployableEntity = m_oBBCoMModeling.getDeployableEntityReferencedByAllocation(oDeployment, oSortedAllocationNames.get(i), m_strCurrentDeviceContainerName);
                if (oDeployableEntity != null) {
                    oDeployableEntities.add(oDeployableEntity);
                }
            }

            requestContainerToLoadDependenciesOfDeployableEntities(oDeployableEntities);
            requestContainerToRegisterInterfaces(oDeployableEntities);
            requestContainerToRegisterEventGroups(oDeployableEntities);
            requestContainerToRegisterPrimitiveComponents(oDeployableEntities);

            requestContainerToCreateDeployableEntities(oDeployableEntities);
            requestContainerToCreateConnectors(oDeployment);
            requestContainerToCreateEventConnectors(oDeployment);
            requestContainerToCreateChannelSourceConnectors(oDeployment);
            requestContainerToCreateChannelSinkConnectors(oDeployment);
            requestContainerToCreateUserInterfacesForSystemLevelProvidedRoles(oDeployment);
            requestContainerToCreateUserInterfacesForSystemLevelSinkRoles(oDeployment);

            informContainerAboutLocalDeploymentCompletion();
        } catch (BBCoMTraversingException oBBCoMTraversingException) {
            throw oBBCoMTraversingException;
        } catch (Exception oException) {
            //TODO: log into log files.
            oException.printStackTrace();

            throw new BBCoMTraversingException(BBCoMMessageUtility.getResourceMessage(BBCoMMessageResource.BBCOMTRAVERSING_UNEXPECTED_EXCEPTION, oException));
        }
    }

    /**
     * Request the container to create user interfaces for system-level sink roles.
     *
     * @param deployment the given deployment
     * @throws Exception if there is an error.
     */
    private void requestContainerToCreateUserInterfacesForSystemLevelSinkRoles(DeploymentType deployment) throws Exception {
        SystemType oSystem = m_oBBCoMModeling.getSystem(deployment);
        for (SinkRoleType oSystemLevelSinkRole : m_oBBCoMModeling.getSinkRoles(oSystem)) {
            String strSystemLevelSinkRoleName = oSystemLevelSinkRole.getName();
            TwoElementsCrossReference oTwoElementsCrossReference = new TwoElementsCrossReference(oSystemLevelSinkRole.getLevelUp());
            String strSinkComponentInstanceName = oTwoElementsCrossReference.getFirstElement();
            String strSinkRoleName = oTwoElementsCrossReference.getSecondElement();

            DeviceContainerType oDeviceContainerOfComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strSinkComponentInstanceName);
            if (m_strCurrentDeviceContainerName.equals(oDeviceContainerOfComponentInstance.getName())) {
                ComponentInstanceType oSinkComponentInstance = m_oBBCoMModeling.getComponentInstance(oSystem, strSinkComponentInstanceName);
                SinkRoleType oSinkRole = m_oBBCoMModeling.getSinkRole(m_oBBCoMModeling.getComponent(oSinkComponentInstance), strSinkRoleName);

                EventGroupType oSinkEventGroup = m_oBBCoMModeling.getEventGroupReferencedByRole(oSinkRole);

                UserInterfaceType oUserInterface = m_oBBCoMModeling.getUserInterface(oSystemLevelSinkRole);

                m_oIContainer.createUserInterfaceForSystemLevelSinkRole(strSystemLevelSinkRoleName, oSinkEventGroup.getName(), strSinkComponentInstanceName, strSinkRoleName, oUserInterface);
            }
        }
    }

    /**
     * Request the container to create user interfaces for system-level provided roles.
     *
     * @param deployment the given deployment.
     * @throws Exception if there is an error.
     */
    private void requestContainerToCreateUserInterfacesForSystemLevelProvidedRoles(DeploymentType deployment) throws Exception {
        SystemType oSystem = m_oBBCoMModeling.getSystem(deployment);
        for (ProvidedRoleType oSystemLevelProvidedRole : m_oBBCoMModeling.getProvidedRoles(oSystem)) {
            String strSystemLevelProvidedRoleName = oSystemLevelProvidedRole.getName();
            TwoElementsCrossReference oTwoElementsCrossReference = new TwoElementsCrossReference(oSystemLevelProvidedRole.getLevelUp());
            String strProvidedComponentInstanceName = oTwoElementsCrossReference.getFirstElement();
            String strProvidedRoleName = oTwoElementsCrossReference.getSecondElement();

            DeviceContainerType oDeviceContainerOfComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strProvidedComponentInstanceName);
            if (m_strCurrentDeviceContainerName.equals(oDeviceContainerOfComponentInstance.getName())) {
                ComponentInstanceType oProvidedComponentInstance = m_oBBCoMModeling.getComponentInstance(oSystem, strProvidedComponentInstanceName);
                ProvidedRoleType oProvidedRole = m_oBBCoMModeling.getProvidedRole(m_oBBCoMModeling.getComponent(oProvidedComponentInstance), strProvidedRoleName);

                InterfaceType oProvidedInterface = m_oBBCoMModeling.getInterfaceReferencedByRole(oProvidedRole);

                UserInterfaceType oUserInterface = m_oBBCoMModeling.getUserInterface(oSystemLevelProvidedRole);

                m_oIContainer.createUserInterfaceForSystemLevelProvidedRole(strSystemLevelProvidedRoleName, oProvidedInterface.getName(), strProvidedComponentInstanceName, strProvidedRoleName, oUserInterface);
            }
        }
    }

    /**
     * Request the container to crate channel sink connectors.
     *
     * @param deployment the given deployment.
     * @throws Exception if there is an error.
     */
    private void requestContainerToCreateChannelSinkConnectors(DeploymentType deployment) throws Exception {
        SystemType oSystem = m_oBBCoMModeling.getSystem(deployment);
        ChannelSinkConnectorType[] oChannelSinkConnectorTypes = m_oBBCoMModeling.getChannelSinkConnectors(oSystem);

        for (ChannelSinkConnectorType oChannelSinkConnectorType : oChannelSinkConnectorTypes) {
            String strRefEventChannel = oChannelSinkConnectorType.getRefEventChannel();

            DeviceContainerType oDeviceContainerOfEventChannel = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strRefEventChannel);
            if (m_strCurrentDeviceContainerName.equals(oDeviceContainerOfEventChannel.getName())) {
                String strRefSinkRole = oChannelSinkConnectorType.getRefSinkRole();
                TwoElementsCrossReference oTwoElementsCrossReference = new TwoElementsCrossReference(strRefSinkRole);
                String strSinkComponentInstanceName = oTwoElementsCrossReference.getFirstElement();

                DeviceContainerType oDeviceContainerOfSinkComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strSinkComponentInstanceName);

                FilterRuleType oFilterRule = m_oBBCoMModeling.getFilterRule(oChannelSinkConnectorType);
                m_oIContainer.createChannelSinkConnector(strRefEventChannel, strSinkComponentInstanceName, oDeviceContainerOfSinkComponentInstance.getName(), oFilterRule);
            }
        }
    }

    /**
     * Request the container to create channel source connectors.
     *
     * @param deployment the given deployment.
     * @throws Exception if there is an error.
     */
    private void requestContainerToCreateChannelSourceConnectors(DeploymentType deployment) throws Exception {
        SystemType oSystem = m_oBBCoMModeling.getSystem(deployment);
        ChannelSourceConnectorType[] oChannelSourceConnectorTypes = m_oBBCoMModeling.getChannelSourceConnectors(oSystem);

        for (ChannelSourceConnectorType oChannelSourceConnectorType : oChannelSourceConnectorTypes) {
            String strRefSourceRole = oChannelSourceConnectorType.getRefSourceRole();
            TwoElementsCrossReference oTwoElementsCrossReference = new TwoElementsCrossReference(strRefSourceRole);
            String strSourceComponentInstanceName = oTwoElementsCrossReference.getFirstElement();
            String strSourceRoleName = oTwoElementsCrossReference.getSecondElement();

            DeviceContainerType oDeviceContainerOfSourceComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strSourceComponentInstanceName);
            if (m_strCurrentDeviceContainerName.equals(oDeviceContainerOfSourceComponentInstance.getName())) {

                ComponentInstanceType oSourceComponentInstance = m_oBBCoMModeling.getComponentInstance(oSystem, strSourceComponentInstanceName);
                SourceRoleType oSourceRole = m_oBBCoMModeling.getSourceRole(m_oBBCoMModeling.getComponent(oSourceComponentInstance), strSourceRoleName);

                EventGroupType oSourceEventGroup = m_oBBCoMModeling.getEventGroupReferencedByRole(oSourceRole);

                String strRefEventChannel = oChannelSourceConnectorType.getRefEventChannel();

                DeviceContainerType oDeviceContainerOfEventChannel = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strRefEventChannel);

                m_oIContainer.createChannelSourceConnector(strSourceComponentInstanceName, strSourceRoleName, oSourceEventGroup.getName(), strRefEventChannel, oDeviceContainerOfEventChannel.getName());
            }
        }
    }

    /**
     * Request the container to create event connectors.
     *
     * @param deployment the deployment.
     * @throws Exception if there is an error.
     */
    private void requestContainerToCreateEventConnectors(DeploymentType deployment) throws Exception {
        SystemType oSystem = m_oBBCoMModeling.getSystem(deployment);
        EventConnectorType[] oEventConnectors = m_oBBCoMModeling.getEventConnectors(oSystem);

        for (EventConnectorType oEventConnector : oEventConnectors) {
            String strRefSourceRole = oEventConnector.getRefSourceRole();
            TwoElementsCrossReference oTwoElementsCrossReference = new TwoElementsCrossReference(strRefSourceRole);
            String strSourceComponentInstanceName = oTwoElementsCrossReference.getFirstElement();
            String strSourceRoleName = oTwoElementsCrossReference.getSecondElement();

            DeviceContainerType oDeviceContainerOfSourceComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strSourceComponentInstanceName);
            if (m_strCurrentDeviceContainerName.equals(oDeviceContainerOfSourceComponentInstance.getName())) {

                ComponentInstanceType oSourceComponentInstance = m_oBBCoMModeling.getComponentInstance(oSystem, strSourceComponentInstanceName);
                SourceRoleType oSourceRole = m_oBBCoMModeling.getSourceRole(m_oBBCoMModeling.getComponent(oSourceComponentInstance), strSourceRoleName);

                EventGroupType oSourceEventGroup = m_oBBCoMModeling.getEventGroupReferencedByRole(oSourceRole);

                String strRefSinkRole = oEventConnector.getRefSinkRole();
                oTwoElementsCrossReference = new TwoElementsCrossReference(strRefSinkRole);
                String strSinkComponentInstanceName = oTwoElementsCrossReference.getFirstElement();

                DeviceContainerType oDeviceContainerOfSinkComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strSinkComponentInstanceName);

                m_oIContainer.createEventConnector(strSourceComponentInstanceName, strSourceRoleName, oSourceEventGroup.getName(), strSinkComponentInstanceName, oDeviceContainerOfSinkComponentInstance.getName());

            }
        }

    }

    /**
     * Inform the container about the local deployment completion.
     */
    private void informContainerAboutLocalDeploymentCompletion() {
        m_oIContainer.informLocalDeploymentCompletion();
    }

    /**
     * Request the container to create connectors.
     *
     * @param deployment the given deployment.
     * @throws Exception if there is an error.
     */
    private void requestContainerToCreateConnectors(DeploymentType deployment) throws Exception {
        SystemType oSystem = m_oBBCoMModeling.getSystem(deployment);
        ConnectorType[] oConnectors = m_oBBCoMModeling.getConnectors(oSystem);
        for (ConnectorType oConnector : oConnectors) {
            String strRefRequiredRole = oConnector.getRefRequiredRole();
            TwoElementsCrossReference oTwoElementsCrossReference = new TwoElementsCrossReference(strRefRequiredRole);
            String strRequiredComponentInstanceName = oTwoElementsCrossReference.getFirstElement();
            String strRequiredRoleName = oTwoElementsCrossReference.getSecondElement();

            DeviceContainerType oDeviceContainerOfRequiredComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strRequiredComponentInstanceName);
            if (m_strCurrentDeviceContainerName.equals(oDeviceContainerOfRequiredComponentInstance.getName())) {

                ComponentInstanceType oRequiredComponentInstance = m_oBBCoMModeling.getComponentInstance(oSystem, strRequiredComponentInstanceName);
                RequiredRoleType oRequiredRole = m_oBBCoMModeling.getRequiredRole(m_oBBCoMModeling.getComponent(oRequiredComponentInstance), strRequiredRoleName);

                InterfaceType oRequiredInterface = m_oBBCoMModeling.getInterfaceReferencedByRole(oRequiredRole);

                String strRefProvidedRole = oConnector.getRefProvidedRole();
                oTwoElementsCrossReference = new TwoElementsCrossReference(strRefProvidedRole);
                String strProvidedComponentInstanceName = oTwoElementsCrossReference.getFirstElement();

                DeviceContainerType oDeviceContainerOfProvidedComponentInstance = m_oBBCoMModeling.getDeviceContainerOfDeployableEntity(deployment, strProvidedComponentInstanceName);

                m_oIContainer.createConnector(strRequiredComponentInstanceName, strRequiredRoleName, oRequiredInterface.getName(), strProvidedComponentInstanceName, oDeviceContainerOfProvidedComponentInstance.getName());

            }
        }
    }


    /**
     * Request the container to register the primitive components of the given deployable entities.
     *
     * @param deployableEntities the given deployable entities.
     */
    private void requestContainerToRegisterPrimitiveComponents(List<DeployableEntityType> deployableEntities) {
        if (deployableEntities == null) {
            return;
        }

        List<PrimitiveComponentType> oPrimitiveComponentsToRegister = new ArrayList<>();

        for (DeployableEntityType oDeployableEntity : deployableEntities) {
            List<PrimitiveComponentType> oPrimitiveComponents = m_oBBCoMModeling.getPrimitiveComponents(oDeployableEntity);
            if (oPrimitiveComponents == null) {
                continue;
            }

            for (PrimitiveComponentType oPrimitiveComponent : oPrimitiveComponents) {
                if (oPrimitiveComponentsToRegister.contains(oPrimitiveComponent)) {
                    continue;
                }

                oPrimitiveComponentsToRegister.add(oPrimitiveComponent);
            }
        }

        m_oIContainer.registerComponents(oPrimitiveComponentsToRegister);
    }

    /**
     * Request the container to register the event groups of the given deployable entities.
     *
     * @param deployableEntities the given deployable entities.
     */
    private void requestContainerToRegisterEventGroups(List<DeployableEntityType> deployableEntities) {
        if (deployableEntities == null) {
            return;
        }

        List<EventGroupType> oEventGroupsToRegister = new ArrayList<>();

        for (DeployableEntityType oDeployableEntity : deployableEntities) {
            List<EventGroupType> oEventGroups = m_oBBCoMModeling.getEventGroups(oDeployableEntity);
            if (oEventGroups == null) {
                continue;
            }

            for (EventGroupType oEventGroup : oEventGroups) {
                if (oEventGroupsToRegister.contains(oEventGroup)) {
                    continue;
                }

                oEventGroupsToRegister.add(oEventGroup);
            }
        }

        m_oIContainer.registerEventGroups(oEventGroupsToRegister);
    }


    /**
     * Request the container to register the interfaces of the given deployable entities.
     *
     * @param deployableEntities the given deployable entities.
     */
    private void requestContainerToRegisterInterfaces(List<DeployableEntityType> deployableEntities) {
        if (deployableEntities == null) {
            return;
        }

        List<InterfaceType> oInterfacesToRegister = new ArrayList<>();

        for (DeployableEntityType oDeployableEntity : deployableEntities) {
            List<InterfaceType> oInterfaces = m_oBBCoMModeling.getInterfaces(oDeployableEntity);
            if (oInterfaces == null) {
                continue;
            }

            for (InterfaceType oInterface : oInterfaces) {
                if (oInterfacesToRegister.contains(oInterface)) {
                    continue;
                }

                oInterfacesToRegister.add(oInterface);
            }
        }

        m_oIContainer.registerInterfaces(oInterfacesToRegister);
    }

    /**
     * Request the container to create deployable entities.
     *
     * @param deployableEntities the deployable entities.
     * @throws Exception if there is an error.
     */
    private void requestContainerToCreateDeployableEntities(List<DeployableEntityType> deployableEntities) throws Exception {
        for (DeployableEntityType oDeployableEntity : deployableEntities) {
            if (oDeployableEntity instanceof EventChannelType) {
                m_oIContainer.createEventChannel((EventChannelType) oDeployableEntity);
            } else if (oDeployableEntity instanceof ComponentInstanceType) {
                m_oIContainer.createComponentInstance((ComponentInstanceType) oDeployableEntity);
                //TODO: request the container to create a component instance of the composite component.
            }
        }


    }

    /**
     * Request the container to load the dependencies of the given deployable entities.
     *
     * @param deployableEntities the given deployable entities.
     * @throws BBCoMTraversingException if there is an error.
     */
    private void requestContainerToLoadDependenciesOfDeployableEntities(List<DeployableEntityType> deployableEntities) throws Exception {
        List<String> oNonDuplicatedDependencies = getNonDuplicatedDependencies(deployableEntities);
        m_oIContainer.loadDependencies(oNonDuplicatedDependencies);
    }

    /**
     * Get the non duplicated dependencies of the given deployable entities.
     *
     * @param deployableEntities the given deployable entities.
     * @return the non duplicated dependencies.
     * @throws Exception if there is an error.
     */
    private List<String> getNonDuplicatedDependencies(List<DeployableEntityType> deployableEntities) throws Exception {
        List<String> oReturnDependencies = new ArrayList<>();

        if (deployableEntities == null) {
            return oReturnDependencies;
        }

        IImplementationParser oIImplementationParser = BBCoMSettings.getInstance().getImplementationParserPlugin();

        for (DeployableEntityType oDeployableEntity : deployableEntities) {
            List<ImplementationType> oImplementations = m_oBBCoMModeling.getImplementations(oDeployableEntity);
            if (oImplementations == null) {
                continue;
            }

            for (ImplementationType oImplementation : oImplementations) {
                List<String> oImplementationDependencies = oIImplementationParser.getImplementationDependencies(oImplementation);
                for (String strImplementationDependency : oImplementationDependencies) {
                    if (!oReturnDependencies.contains(strImplementationDependency)) {
                        oReturnDependencies.add(strImplementationDependency);
                    }
                }
            }
        }

        return oReturnDependencies;
    }

    /**
     * Request the container to stop.
     */
    public void requestContainerToStop() {
        if (m_oIContainer != null) {
            m_oIContainer.stop();
        }
    }


    class TwoElementsCrossReference {
        private String m_strFirstElement;
        private String m_strSecondElement;


        /**
         * The constructor.
         *
         * @param twoElementsReference the two elements reference.
         */
        public TwoElementsCrossReference(String twoElementsReference) {
            String[] oElements = twoElementsReference.split("/");

            m_strFirstElement = oElements[0];
            m_strSecondElement = oElements[1];
        }

        /**
         * Get the first element.
         *
         * @return the first element.
         */
        public String getFirstElement() {
            return m_strFirstElement;
        }

        /**
         * Get the second element.
         *
         * @return the second element.
         */
        public String getSecondElement() {
            return m_strSecondElement;
        }
    }
}
